接近年底,想分享点儿东西给大家。
Android UI绘制过程
开发中的卡顿我想没跟人都遇到过,之前也是搜博客看看怎么个解决办法,没有认真研究过,今天我打算跟大家聊一聊。
先从View 说吧。相信大家应该都知道View的绘制过程,measure,layout,draw。丢帧一定是在16ms内没有把这些事儿干完就对了,这里我们简单的分一下,主要是计算时间,以及绘图时间。
计算时间:这里的measure,layout的过程,都是会向下递归计算的,学过数据结构的话,应该知道,深搜的代价是很大的。所以尽量让树的高度降低,这里就引出扁平化布局。
绘图时间:这里需要着重讲一下,因为有时候这才是我们UI卡顿的主要原因。在这里我们要把android的试图看成是三维的,就像photoshop的图层一样。android在绘制的时候就会一层一层的“粉刷”,好了,那么造成卡顿,也就是丢帧,说白了最后没有在16ms内做完。好了,让我们剖析一下:
invalidate():
我们知道invalidate 是用来请求View 重绘的
|
|
invalidateInternal
这里可以看出来draw的过程其实就是拿到AttachInfo 里面包含着绘制信息,以及将绘制区域拿到,通过parent去绘制。让我们跟进去。
|
|
invalidateChildInParent
这里的dirty代表你绘制的这块区域是否透明。
|
|
invalidate
这里我们看到了个关键函数 scheduleTraversals ,为什么说神奇。我们看一下
|
|
scheduleTraversals
这里最重要的是Choreographer 这个,我们最终算出来的绘制信息都要通过它回调,开始他会注册一个广播用来接收时钟信息,然后他会在内部建立一个UI绘制队列:CallbackQueue,我们在外部CallBack的时候,会将我们的绘制信息作为CallbackRecord 然后会在接收到一个时钟信号的时候进行doFrame操作,并打印Traces信息,从而来绘制一帧。
|
|
CallbackQueue and CallbackRecord
|
|
postCallbackDelayedInternal
可以看到这里我们把我们的绘制内容扔到队列里,等待轮训。
|
|
FrameDisplayEventReceiver
接收时钟脉冲信号的广播,16ms一次,我们的目的就是在这个时钟脉冲里搞定整个 view
Android 动画
Animator,ScrollTo,offsetLeftAndRight,这里面我们先单列这几项,都是同一个原理。这里我们可以大胆的猜想,一定是频繁执行我们的 Choreographer.CallBack 来绘制,因为只要在16ms内绘制成功,那就是流畅的动画。下面我们验证一下
ScrollTo:
我们先看一下 View 中这个方法
|
|
scrollTo
很简单,我们都可以看懂,开始位置,结束位置,这里我们重点关注 postInvalidateOnAnimation() 这个方法
|
|
postInvalidateOnAnimation
我们可以看到,这里的动画过程绘制他还是扔到了ViewRootImpl 代理做这件事。
|
|
dispatchInvalidateOnAnimation
这里我们看到他开了个线程 mInvalidateOnAnimationRunnable 去添加我们这个将要绘制的 view,接下来我们继续庖丁解牛
|
|
InvalidateOnAnimationRunnable
终于,应了我们的猜想,ViewRootImpl 有一个专门执行动画绘制操作的线程,我们可以看到 run() 里面不断地CallBack,然后回收,当然里面有些线程锁啥的不涉及本文就不细说了。
ValueAnimator:
这里我们有个 AnimationHandler 来执行动画操作,这其中我们可以看到
|
|
doAnimationFrame
这里在不断循环我们所有的anim,并在不断执行 scheduleAnimation 方法
|
|
scheduleAnimation
剩下的大家自己翻阅源码把。
这里总结一下。我们所有界面上视图的变化都是都是 ViewRootImpl 把需要重绘的东西填充 Choreographer 中的 mCallbackQueues 队列,然后在时钟脉冲的广播下进行轮训执行。
既然提到队列,假如我们在16ms内大量的填充 AttachInfo 之类的绘制OBJ,就会导致无法再一次时钟脉冲内绘制完毕,就会在造成丢帧,UI阻塞。
避免 Android UI 卡顿解决办法
解决办法:分析了好多,这里说两个方法。
1.避免重绘,这里避免图层(View)迭代。这里我们可以去开发者模式中对“显示GPU视图更新”打钩
过度绘制
优化以后
这里引用 http://hukai.me/android-performance-render 这篇博客的作者,盗个图。😂
这里可以进行,选择制定画布绘制,而不是整个view去绘制。可以在onDraw中进行限制,去限制绘制区域,例如
canvas.clipRect(100,100,350,600, Region.Op.INTERSECT);
2.扁平化布局,归根结底也是减少 mCallbackQueues 队列大小。保证尽量在16ms内绘制完毕,再有就是可以减少视图 ViewTree 的高度,减少时间复杂度,从而优化计算过程
xml代码
优化后的xml代码
*附:
绘制层级
通过打开刚才说的开发者选项,来根据颜色来判断页面绘制情况。
距离回家还有8 个小时,17年希望可以发觉更多的东西给大家,并且希望大家可以积极指出文章中的错误。祝大家新年快乐!😄